﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Globalization;
using System.Windows.Controls.Primitives;
using System.Windows.Media.Effects;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Xml.Serialization;
using System.IO;
using System.Windows.Markup;


namespace proj4
{
    public class BoolToStringConverter : BoolToValueConverter<String> { }
    public class BoolToVisibilityConverter : BoolToValueConverter<Visibility> { }
    public class points
    {
        public points(double _left, double _right, double _top, double _bottom)
        {
            Left = _left; Right = _right; Top = _top; Bottom = _bottom;
        }
        public double Left { get; set; }
        public double Right { get; set; }
        public double Top { get; set; }
        public double Bottom { get; set; }
    };
    public class GameData
    {
        public string Name { get; set; }
        public int Count { get; set; }
        public int Time { get; set; }
    }
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    /// 
    [Serializable]
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private DispatcherTimer timer = new DispatcherTimer();
        private DropShadowEffect myDropShadowEffect;
        private List<List<Thumb>> linkedElements = new List<List<Thumb>>();
        private List<points> corners = new List<points>();
        
        private ObservableCollection<GameData> _easyResult = new ObservableCollection<GameData>();
        private ObservableCollection<GameData> _mediumResult = new ObservableCollection<GameData>();
        private ObservableCollection<GameData> _hardResult = new ObservableCollection<GameData>();

        
        public event PropertyChangedEventHandler PropertyChanged;

        private int matched = 1;
        private int _puzzleSize = 50;
        private int level;
        private int time = 0;

        public int puzzleSize { get { return _puzzleSize; } set { _puzzleSize = value; OnPropertyChanged("puzzleSize"); } }
        public ObservableCollection<GameData> EasyResult { get { return _easyResult; } set { _easyResult = value; OnPropertyChanged("EasyResult"); } }
        public ObservableCollection<GameData> MediumResult { get { return _mediumResult; } set { _mediumResult = value; OnPropertyChanged("MediumResult"); } }
        public ObservableCollection<GameData> HardResult { get { return _hardResult; } set { _hardResult = value; OnPropertyChanged("HardResult"); } }

        public MainWindow()
        {
            InitializeComponent();
            timer.Tick += new EventHandler(timer_Tick);
            timer.Interval = new TimeSpan(0, 0, 1);
            
            initStyle();
        }

        static public void SerializeToXML(ObservableCollection<GameData> list)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(ObservableCollection<GameData>));
            TextWriter textWriter = new StreamWriter(@"D:\Temp\list.xml", false);
            serializer.Serialize(textWriter, list);
            textWriter.Close();
        }

        static public void DeserializeFromXML(ObservableCollection<GameData> list)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(ObservableCollection<GameData>));
            TextReader textReader = null;
            try
            {
                textReader = new StreamReader(@"D:\Temp\list.xml");
                
                serializer.Deserialize(textReader);
                textReader.Close();
                
            }
            catch (FileNotFoundException)
            {
                
            }
                
            
        }

        protected void OnPropertyChanged(string PropertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
        }

        private void initStyle()
        {
            Color ShadowColor = new Color();
            ShadowColor.A = 255;
            ShadowColor.B = 50;
            ShadowColor.G = 50;
            ShadowColor.R = 50;

            myDropShadowEffect = new DropShadowEffect { Color = ShadowColor, ShadowDepth = 15, Opacity = 0.4 };

        }

        private void createPuzzle(BitmapImage img)
        {
            int n = (int)img.DecodePixelWidth / level;
            int m = (int)img.DecodePixelHeight / level;
            int count = 0;
            Random rnd = new Random();
            Thumb nThumb;

            for (int i = 0; i < n; i++)
                for (int j = 0; j < m; j++)
                {
                    ImageBrush ib = new ImageBrush();
                    ib.ImageSource = new CroppedBitmap(img, new Int32Rect(i * level, j * level, level, level));

                    nThumb = new Thumb();

                    nThumb.Width = level;
                    nThumb.Height = level;
                    nThumb.Background = ib;
                    nThumb.DragDelta += Thumb_DragDelta;
                    nThumb.DragStarted += Thumb_DragStarted;
                    nThumb.DragCompleted += Thumb_DragCompleted;
                    nThumb.MouseRightButtonDown += Thumb_MouseRightButtonDown;

                    Canvas.SetLeft(nThumb, rnd.Next(0, (int)picture.ActualWidth - level));
                    Canvas.SetTop(nThumb, rnd.Next(0, (int)picture.ActualHeight - level));

                    nThumb.Tag = new ThumbInfo(i, j, count++, ref nThumb);

                    linkedElements.Add(new List<Thumb>());
                    linkedElements[count - 1].Add(nThumb);
                    corners.Add(new points(Canvas.GetLeft(nThumb), Canvas.GetLeft(nThumb) + nThumb.Width,
                        Canvas.GetTop(nThumb), Canvas.GetTop(nThumb) + nThumb.Height));

                    picture.Children.Add(nThumb);

                }
        }

        private void startButton_Click(object sender, RoutedEventArgs e)
        {
            if (!pauseButton.IsEnabled)
            {
                var dialog = new Microsoft.Win32.OpenFileDialog();
                dialog.Filter = "Image Files (*.bmp, *.jpg *.png)|*.bmp;*.jpg;*png";
                bool? result = dialog.ShowDialog();
                BitmapImage bi = new BitmapImage();

                if (result == true)
                {
                    pauseButton.IsEnabled = true;
                    level = puzzleSize;
                    bi.BeginInit();
                    bi.DecodePixelHeight = 450;
                    bi.DecodePixelWidth = 600;
                    bi.UriSource = new Uri(dialog.FileName, UriKind.Relative);
                    bi.EndInit();
                    createPuzzle(bi);
                    timer.Start();
                }
            }
            else
                reportResult();

        }

        private void reportResult()
        {
            var messageWin = new ResultWindow();
            messageWin.Owner = Application.Current.MainWindow;
            bool? result = messageWin.ShowDialog(time, matched, matched == linkedElements.Count);
            if (result == true)
            {
                ObservableCollection<GameData> tempSorted = new ObservableCollection<GameData>();
                switch (level)
                {
                    case 50:
                        tempSorted = new ObservableCollection<GameData>(HardResult);
                        tempSorted.Add(new GameData { Name = messageWin.usrName, Count = matched, Time = time });
                        tempSorted = new ObservableCollection<GameData>(tempSorted.OrderByDescending(item => item.Count).ThenBy(item => item.Time));
                        HardResult = tempSorted;

                        break;
                    case 75:
                        tempSorted = new ObservableCollection<GameData>(MediumResult);
                        tempSorted.Add(new GameData { Name = messageWin.usrName, Count = matched, Time = time });
                        tempSorted = new ObservableCollection<GameData>(tempSorted.OrderByDescending(item => item.Count).ThenBy(item => item.Time));
                        MediumResult = tempSorted;

                        break;
                    case 150:
                        tempSorted = new ObservableCollection<GameData>(EasyResult);
                        tempSorted.Add(new GameData { Name = messageWin.usrName, Count = matched, Time = time });
                        tempSorted = new ObservableCollection<GameData>(tempSorted.OrderByDescending(item => item.Count).ThenBy(item => item.Time));
                        EasyResult = tempSorted;

                        break;
                }

                clearBoard();
            }
            else if (matched == linkedElements.Count) clearBoard();
        }

        private void clearBoard()
        {
            matched = 1;
            time = 0;
            timer.Stop();
            TimeSpan newTime = this.ConvertToTimeSpan();
            timeBar.Content = String.Format(CultureInfo.CurrentCulture, "Time: {0:00}:{1:00}:{2:00}", newTime.Hours, newTime.Minutes, newTime.Seconds);
            pauseButton.IsEnabled = false;
            linkedElements.Clear();
            corners.Clear();
            picture.Children.Clear();

        }

        private void Thumb_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            var part = sender as Thumb;
            var ti = part.Tag as ThumbInfo;
            if (!ti.used)
            {
                ImageBrush ib = new ImageBrush();
                ib = (ImageBrush)part.Background;

                RotateTransform transform = new RotateTransform();
                transform.CenterX = part.Width / 2 - 2;
                transform.CenterY = part.Height / 2 - 2;
                ti.angle = ti.angle % 360 + 90;
                transform.Angle = ti.angle;
                ti.angle %= 360;

                ib.Transform = transform;

                part.Background = ib;
                part.Tag = ti;
            }
            Thumb_DragCompleted(part, new DragCompletedEventArgs(0, 0, false));

        }

        private TimeSpan ConvertToTimeSpan()
        {
            var hours = time / 3600;
            var minutes = (time % 3600) / 60;
            var seconds = time % 60;
            return new TimeSpan(hours, minutes, seconds);
        }

        private void timer_Tick(object sender, EventArgs e)
        {
            time++;
            TimeSpan newTime = this.ConvertToTimeSpan();
            timeBar.Content = String.Format(CultureInfo.CurrentCulture, "Time: {0:00}:{1:00}:{2:00}", newTime.Hours, newTime.Minutes, newTime.Seconds);
        }

        private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
        {
            var part = sender as Thumb;
            var ti = part.Tag as ThumbInfo;
            double x = e.HorizontalChange;
            double y = e.VerticalChange;
            int id = ti.id;

            //Debug.WriteLine("left {0} top {1} right {2} bottom {3}", corners[ti.id].Left, corners[ti.id].Top, corners[ti.id].Right, corners[ti.id].Bottom);

            placeItems(id, x, y, new Size(picture.ActualWidth, picture.ActualHeight), true);

        }

        private void placeItems(int id, double x, double y, Size length, bool zId)
        {
            if (corners[id].Right + x <= length.Width && corners[id].Left + x >= 0)
            {
                foreach (Thumb el in linkedElements[id])
                    Canvas.SetLeft(el, Canvas.GetLeft(el) + x);
                corners[id].Left += x;
                corners[id].Right += x;
            }

            if (corners[id].Bottom + y <= length.Height && corners[id].Top + y >= 0)
            {
                foreach (Thumb el in linkedElements[id])
                {
                    Canvas.SetTop(el, Canvas.GetTop(el) + y);
                    if (zId)
                        Canvas.SetZIndex(el, time);
                }

                corners[id].Top += y;
                corners[id].Bottom += y;
            }
        }

        private void moveItems(int id, Thumb part, double x, double y)
        {
            double shiftX = Canvas.GetLeft(part) - x;
            double shiftY = Canvas.GetTop(part) - y;
            foreach (Thumb el in linkedElements[id])
            {
                if (el != part)
                {
                    Canvas.SetLeft(el, Canvas.GetLeft(el) - shiftX);
                    Canvas.SetTop(el, Canvas.GetTop(el) - shiftY);
                }
                else
                {
                    Canvas.SetLeft(el, x);
                    Canvas.SetTop(el, y);
                }
            }
            corners[id].Left -= shiftX;
            corners[id].Right -= shiftX;

            corners[id].Top -= shiftY;
            corners[id].Bottom -= shiftY;

        }

        private void Thumb_DragStarted(object sender, DragStartedEventArgs e)
        {
            var part = sender as Thumb;
            ThumbInfo ti = part.Tag as ThumbInfo;
            foreach (Thumb el in linkedElements[ti.id])
                el.Effect = myDropShadowEffect;

        }

        private void Thumb_DragCompleted(object sender, DragCompletedEventArgs e)
        {
            var part = sender as Thumb;
            ThumbInfo ti = part.Tag as ThumbInfo;
            ThumbInfo ti2;

            foreach (Thumb el in linkedElements[ti.id])
                el.Effect = null;

            foreach (Thumb el in picture.Children)
            {
                ti2 = el.Tag as ThumbInfo;
                if (ti2.angle == 0 && ti.angle == 0 && el != part && ti2.id != ti.id)
                {
                    if (Canvas.GetLeft(el) <= Canvas.GetLeft(part) + level + 5 && Canvas.GetLeft(el) >= Canvas.GetLeft(part) + level - 5
                         && Canvas.GetTop(el) <= Canvas.GetTop(part) + 5 && Canvas.GetTop(el) >= Canvas.GetTop(part) - 5
                         && (ti.pt.X + 1 == ti2.pt.X && ti.pt.Y == ti2.pt.Y))//lewa
                    {
                        ti2.used = true;
                        el.Tag = ti2;
                        moveItems(ti.id, part, Canvas.GetLeft(el) - level, Canvas.GetTop(el));
                        Union(ti.id, ti2.id);
                        matched++;

                    }

                    else if (Canvas.GetLeft(el) <= Canvas.GetLeft(part) + 5 && Canvas.GetLeft(el) >= Canvas.GetLeft(part) - 5
                        && Canvas.GetTop(el) <= Canvas.GetTop(part) + level + 5 && Canvas.GetTop(el) >= Canvas.GetTop(part) + level - 5
                        && (ti.pt.Y + 1 == ti2.pt.Y && ti.pt.X == ti2.pt.X))//góra
                    {
                        ti2.used = true;
                        el.Tag = ti2;
                        moveItems(ti.id, part, Canvas.GetLeft(el), Canvas.GetTop(el) - level);
                        Union(ti.id, ti2.id);
                        matched++;

                    }

                    else if (Canvas.GetLeft(el) <= Canvas.GetLeft(part) - level + 5 && Canvas.GetLeft(el) >= Canvas.GetLeft(part) - level - 5
                         && Canvas.GetTop(el) <= Canvas.GetTop(part) + 5 && Canvas.GetTop(el) >= Canvas.GetTop(part) - 5
                         && (ti.pt.X - 1 == ti2.pt.X && ti.pt.Y == ti2.pt.Y))//prawa
                    {
                        ti2.used = true;
                        el.Tag = ti2;
                        moveItems(ti.id, part, Canvas.GetLeft(el) + level, Canvas.GetTop(el));
                        Union(ti.id, ti2.id);
                        matched++;

                    }

                    else if (Canvas.GetLeft(el) <= Canvas.GetLeft(part) + 5 && Canvas.GetLeft(el) >= Canvas.GetLeft(part) - 5
                        && Canvas.GetTop(el) <= Canvas.GetTop(part) - level + 5 && Canvas.GetTop(el) >= Canvas.GetTop(part) - level - 5
                        && (ti.pt.Y - 1 == ti2.pt.Y && ti.pt.X == ti2.pt.X))//dół
                    {
                        ti2.used = true;
                        el.Tag = ti2;
                        moveItems(ti.id, part, Canvas.GetLeft(el), Canvas.GetTop(el) + level);
                        Union(ti.id, ti2.id);
                        matched++;

                    }
                }
            }
            if (matched == linkedElements.Count)
                reportResult();

            else if (linkedElements[ti.id].Count > 1)
            {
                ti.used = true;
                part.Tag = ti;
            }

        }

        private void Union(int id1, int id2)
        {

            if (linkedElements[id1].Count < linkedElements[id2].Count)
            {
                int id = id2;
                id2 = id1;
                id1 = id;
            }

            corners[id1].Left = Math.Min(corners[id1].Left, corners[id2].Left);
            corners[id1].Right = Math.Max(corners[id1].Right, corners[id2].Right);
            corners[id1].Top = Math.Min(corners[id1].Top, corners[id2].Top);
            corners[id1].Bottom = Math.Max(corners[id1].Bottom, corners[id2].Bottom);


            foreach (Thumb el in linkedElements[id2])
            {
                var ti = el.Tag as ThumbInfo;
                ti.id = id1;
                el.Tag = ti;
                linkedElements[id1].Add(el);
            }

            linkedElements[id2].Clear();

        }

        private void Story_Completed(object sender, EventArgs e)
        {
            pauseImage.Visibility = Visibility.Hidden;
            timer.Start();
        }

        private void picture_SizeChanged(object sender, SizeChangedEventArgs e)
        {

            double diffWidth = e.NewSize.Width - e.PreviousSize.Width;
            double diffHeight = e.NewSize.Height - e.PreviousSize.Height;
            double diffX = 0, diffY = 0;
            int count = 0;
            //Debug.WriteLine("diffWidth {0} diffHeight {1}", diffWidth, diffHeight);
            if (corners.Count > 0)
            {
                for (int i = 0; i < corners.Count; i++)
                {

                    if (linkedElements[i].Count > 0 && (corners[i].Right >= e.NewSize.Width) ||
                        (corners[i].Bottom >= e.NewSize.Height))
                    {
                        if (e.WidthChanged && (corners[i].Right >= e.NewSize.Width))
                            diffX = e.NewSize.Width - corners[i].Right;
                        if (e.HeightChanged && (corners[i].Bottom >= e.NewSize.Height))
                            diffY = e.NewSize.Height - corners[i].Bottom;
                        placeItems(i, diffX, diffY, e.NewSize, false);
                        //Debug.WriteLine("diffX {0} diffY {1}", diffX, diffY);
                        count++;

                    }
                }

            }
            //Debug.WriteLine(count);
        }

        private void pauseButton_Click(object sender, RoutedEventArgs e)
        {
            pauseImage.Visibility = Visibility.Visible;
            timer.Stop();
        }

        private void Window_Closed(object sender, EventArgs e)
        {
            SerializeToXML(_easyResult);
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            DeserializeFromXML(_easyResult);
            OnPropertyChanged("EasyResult");
        }

    }
}
